// http://www.webgl3d.cn/threejs/docs/scenes/bones-browser.html
import { 
  Bone,
  Clock,
  CylinderBufferGeometry,
  DoubleSide,
  Float32BufferAttribute,
  MeshPhongMaterial,
  PerspectiveCamera,
  PointLight,
  Scene, 
  Skeleton, 
  SkeletonHelper, 
  SkinnedMesh, 
  Uint16BufferAttribute, 
  Vector3, 
  WebGLRenderer
} from "three";
import stats from '../../utils/stats';
// 调用
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GUI } from 'dat.gui';

let clock = new Clock();
let scene = new Scene();
let camera = new PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 200);
camera.position.set(0, 30, 30);
camera.lookAt(scene.position);

const renderer = new WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const lights = [];
lights[0] = new PointLight(0xffffff, 1, 0);
lights[1] = new PointLight(0xffffff, 1, 0);
lights[2] = new PointLight(0xffffff, 1, 0);

lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);

scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);

let state = {
  animateBones: false,
}
let mesh: SkinnedMesh

initBones();
setupDatGui();

const trackballControls = new OrbitControls(camera, renderer.domElement);

render();

function render() {
  stats.update();
  trackballControls.update();

  let time = Date.now() * 0.001;

  if (state.animateBones) {
    let len = mesh.skeleton.bones.length
    for (let i = 0; i < len; i++) {
      mesh.skeleton.bones[i].rotation.z = Math.sin(time) * 2 / len;
    }
  }

  requestAnimationFrame(render);
  renderer.render(scene, camera);
}

function initBones() {
  // 每段的高度
  let segmentHeight = 8;
  // 段数
  let segmentCount = 4;
  // 总高度
  let height = segmentHeight * segmentCount;
  // 一半高度
  let halfHeight = height * 0.5;
  let sizing = {
    segmentHeight,
    segmentCount,
    height,
    halfHeight,
  }

  let geometry = createGeometry(sizing);
  let bones = createBones(sizing);
  mesh = createMesh(geometry, bones);

  mesh.scale.multiplyScalar(1);
  scene.add(mesh);
}

/**
 * 创建几何
 * @param sizing 
 */
function createGeometry(sizing: any): CylinderBufferGeometry {
  // 创建一个圆柱体
  // https://threejs.org/docs/index.html#api/zh/geometries/CylinderBufferGeometry
  let geometry = new CylinderBufferGeometry(
    5, // radiusTop 上半径
    5, // radiusBottom 下半径
    sizing.height, // height 高度
    8, // radiusSegments 圆形面分段数
    sizing.segmentCount * 3, // heightSegments 沿高度分段数 4 * 3
    true, // openEnded 指明该圆锥的底面是开放的还是封顶的。默认值为false
  );
  // 圆柱体顶点位置集合
  let position = geometry.attributes.position;
  // 创建一个三维向量用于保存顶点坐标
  let vertex = new Vector3();
  // 顶点索引聚合
  let skinIndices = [];
  // 顶点权重聚合
  let skinWeights = [];
  
  // 遍历顶点
  for (let i = 0; i < position.count; i++) {
    // 依次取出每个点
    vertex.fromBufferAttribute(position, i);
    // y 保存相对于圆柱体底面的高度值
    let y = (vertex.y + sizing.halfHeight);
    // 高度除以总高度在向下取整
    let skinIndex = Math.floor(y / sizing.segmentHeight);
    // 当前 y 值占该段的百分比
    let skinWeight = (y % sizing.segmentHeight) / sizing.segmentHeight;

    // 该点关联bone[skinIndex]和bone[skinIndex+1]
    skinIndices.push(skinIndex, skinIndex + 1, 0, 0);
    // 关联bone[skinIndex]的比重为1 - skinWeight，关联bone[skinIndex+1]的比重为skinWeight。
    skinWeights.push(1 - skinWeight, skinWeight, 0, 0);
  }

  // 几何体中添加skinIndex属性
  geometry.setAttribute('skinIndex', new Uint16BufferAttribute(skinIndices, 4));
  // 几何体中添加skinWeight属性
  geometry.setAttribute('skinWeight', new Float32BufferAttribute(skinWeights, 4));

  return geometry
}

function createBones(sizing: any) {
  let bones = [];

  // //根骨骼节点
  let prevBone = new Bone();
  //为根骨骼添加位置
  prevBone.position.y = - sizing.halfHeight;

  bones.push(prevBone);

  for (let i = 0; i < sizing.segmentCount; i++) {
    // 创建骨骼节点
    let bone = new Bone();
    // 为骨骼节点添加本地位置 虽然本地设置的位置都是一样的，但是由于这些骨骼都是父子关系，所以在世界坐标系上位置不同
    bone.position.y = sizing.segmentHeight;
    // 数组中继续添加骨骼
    bones.push(bone);
    // 根骨骼添加当前骨骼
    prevBone.add(bone);
    // 再将当前骨骼赋值给根骨骼
    prevBone = bone;
  }

  return bones;
}

/**
 * 创建蒙皮网格并添加骨骼助手
 */
function createMesh(geometry: CylinderBufferGeometry, bones: any): SkinnedMesh {
  let material = new MeshPhongMaterial({
    skinning: true,
    color: 0x156289,
    emissive: 0x072534,
    side: DoubleSide,
    flatShading: true
  })

  // 创建蒙皮网格
  let mesh = new SkinnedMesh(geometry, material);
  // 创建骨架
  let skeleton = new Skeleton(bones);

  // 网格添加根骨骼节点
  mesh.add(bones[0]);
  // 网格绑定骨架
  mesh.bind(skeleton);

  // 创建骨骼显示助手
  let skeletonHelper = new SkeletonHelper(mesh);
  scene.add( skeletonHelper )

  return mesh
}

function setupDatGui() {
  let gui = new GUI();
  let floder = gui.addFolder('基础设置');
  floder.add(state, 'animateBones').name('启用骨骼动画');
  floder.add(mesh, 'pose').name('pose回正');

  let bones = mesh.skeleton.bones;

  for (let i = 0; i < bones.length; i++) {
    let bone = bones[i];

    floder = gui.addFolder('Bone' + i);

    floder.add(bone.position, 'x', -10 + bone.position.x, 10+ bone.position.x).name('position.x');
    floder.add(bone.position, 'y', -10 + bone.position.y, 10+ bone.position.y).name('position.y');
    floder.add(bone.position, 'z', -10 + bone.position.z, 10+ bone.position.z).name('position.z');

    floder.add(bone.rotation, 'x', -Math.PI * 0.5, Math.PI * 0.5).name('rotation.x');
    floder.add(bone.rotation, 'y', -Math.PI * 0.5, Math.PI * 0.5).name('rotation.y');
    floder.add(bone.rotation, 'z', -Math.PI * 0.5, Math.PI * 0.5).name('rotation.z');
    
    floder.add(bone.scale, 'x', 0, 2).name('scale.x');
    floder.add(bone.scale, 'y', 0, 2).name('scale.y');
    floder.add(bone.scale, 'z', 0, 2).name('scale.z');
  }
}